3 overrideBackends: false,
10 // Mostly just for debugging, store the cookie string value here
11 // rather than in the sub-function scope
14 // An object representation of the cookie. This is converted from the
15 // XML cookie value on init. The form controls will manipulate this,
16 // and when the user clicks "Go", this will be converted back into
20 ///////////////////////////////////////////////////////////////////////////////
21 function cbChanged(event) {
22 //console.info("Event caught: " + event);
23 var target = $j(event.target);
24 var id = target.attr("id");
25 var value = target.attr("value");
26 var checked = target.attr("checked");
27 /*console.info("target id: '" + id +
28 "', value: '" + value +
29 "', checked: '" + checked + "'");*/
32 if (id == "besetsel-cb") {
34 $j("#besetsel-sel").removeAttr("disabled");
38 $j("#besetsel-sel").attr("disabled", 1);
39 delete cookieObj.besetName;
42 else if (id == "besetsel-sel") {
47 if (m = id.match(/besetsel-be-(.*?)-cb/)) {
49 //console.info(">>>backend checkbox: " + backend);
51 $j("#besetsel-be-" + backend + "-text").removeAttr("disabled");
52 beUrlFormToObj(backend);
55 $j("#besetsel-be-" + backend + "-text").attr("disabled", 1);
56 delete cookieObj.backendUrls[backend];
59 else if (m = id.match(/besetsel-be-(.*?)-text/)) {
61 //console.info(">>>backend text: " + backend);
62 beUrlFormToObj(backend);
66 // PMC-11784 and PMC-11785.
67 // This fixes a nasty IE bug. It causes a slight flash when the user
68 // clicks a checkbox, but it works.
69 if (jQuery.browser.msie){
71 window.setTimeout( function(){ target.show();}, 0 );
76 ///////////////////////////////////////////////////////////////////////////////
77 // besetSelFormToObj()
78 // This is called by a couple of event handlers and decodes the
79 // currently selected BESet (in the drop-down form) and sets the
80 // cookieObj.besetName accordingly.
82 function besetSelFormToObj()
84 cookieObj.besetName = $j("#besetsel-sel").val();
87 ///////////////////////////////////////////////////////////////////////////////
88 // beUrlFormToObj(backend)
89 // This is similar, and takes care of reading the text value from the
90 // form and stuffing it into the object
92 function beUrlFormToObj(backend) {
93 var value = $j("#besetsel-be-" + backend + "-text").attr("value");
94 if (value) cookieObj.backendUrls[backend] = value;
97 ///////////////////////////////////////////////////////////////////////////////
99 if ($j("#besetsel-form").length < 1)
104 cookieName = $j("#besetsel-form").attr("cookieName");
105 cookieObj = cookieXmlToJson(cookieName);
109 $j("#besetsel-form .besetsel-control").change(function(event) {
112 $j("#besetsel-go-button").click(function(event) {
115 $j("#besetsel-reset-button").click(function(event) {
119 // This "pullout" might be empty, in the case of the BESet being
120 // selected by path segment instead of cookie. In that case, the
121 // tab acts as a watermark, just to identify the BESet, and we
122 // don't want to allow it to be "pulled out". So we'll set the
123 // width to 0 in that case.
124 var w = $j("#besetsel-go-button").length > 0 ? "400px" : "0px";
126 // Put it into the sidecontent pullout
127 $j("#besetsel-form").sidecontent({
128 /*classmodifier: "besetsel",*/
129 attachto: "rightside",
133 textdirection: "vertical",
138 var pulloutColor = $j("#besetsel-form").attr("pulloutColor");
139 //alert("color is " + pulloutColor);
140 $j("#besetsel-form").data("pullout").css("background-color", pulloutColor || '#663854');
142 if ($j("#besetsel-go-button").size() > 0) {
143 $j("#besetsel-form").data("pullout").css({
144 "border-top": "ridge gray 5px",
145 "border-bottom": "ridge gray 5px",
146 "border-left": "ridge gray 5px"
151 ///////////////////////////////////////////////////////////////////////////////
153 // Handle the user-click of the "Go!" button.
155 function goButton(event) {
156 // Convert the object into XML
157 var cookieXml = "<Backends><BESet" + ( cookieObj.besetName ? (" name='" + cookieObj.besetName + "'>") : ">" );
158 for (var backend in cookieObj.backendUrls) {
159 //console.info("+++ backend " + backend);
161 "<Backend name='" + backend + "'>" + xmlEscape(cookieObj.backendUrls[backend]) + "</Backend>";
163 cookieXml += "</BESet></Backends>";
164 //console.info(cookieXml);
167 document.cookie = cookieName + "=" + encodeURIComponent(cookieXml) +
172 window.location.reload();
175 ///////////////////////////////////////////////////////////////////////////////
176 // resetButton(event)
177 // Handle the user-click of the "Reset" button.
178 // Does the same thing as "Go!", but sets the cookie to the empty string.
180 function resetButton(event) {
182 document.cookie = cookieName + "=" +
187 window.location.reload();
190 ///////////////////////////////////////////////////////////////////////////////
191 function xmlEscape(str) {
192 str = str.replace(/\&/g, '&')
193 .replace(/\</g, '<')
194 .replace(/\>/g, '>')
195 .replace(/\"/g, '"')
196 .replace(/\'/g, ''');
200 ///////////////////////////////////////////////////////////////////////////////
201 // This function reads the cookie value and initializes the form state
202 // Don't assume anything about the form state -- redo everything.
203 function initFormState() {
205 var besetName = cookieObj.besetName;
208 $j("#besetsel-cb").removeAttr("checked");
209 $j("#besetsel-sel").attr("disabled", 1);
212 var selBESet = $j("#besetsel-opt-" + besetName);
213 if (selBESet.length != 0) {
214 $j("#besetsel-cb").attr("checked", 1);
215 $j("#besetsel-sel").removeAttr("disabled");
216 selBESet.attr("selected", 1);
219 $j("#besetsel-cb").removeAttr("checked");
220 $j("#besetsel-sel").attr("disabled", 1);
224 // Foreach backend in the form
225 $j(".besetsel-be-cb").each(function(i) {
226 var id = $j(this).attr("id");
227 var beName = id.match(/besetsel-be-(.*?)-cb/)[1];
228 //console.info("### backend, id is '" + id + "', beName is '" + beName + "'");
231 // See if there's a corresponding element in the cookie
232 if (!cookieObj.backendUrls ||
233 !cookieObj.backendUrls[beName]) {
234 //console.info("Didn't find " + beName);
235 $j("#besetsel-be-" + beName + "-cb").removeAttr("checked");
236 $j("#besetsel-be-" + beName + "-text").attr("disabled", 1);
239 //console.info("Found " + beName);
240 $j("#besetsel-be-" + beName + "-cb").attr("checked", 1);
241 var textbox = $j("#besetsel-be-" + beName + "-text");
242 textbox.removeAttr("disabled");
243 textbox.attr("value", cookieObj.backendUrls[beName]);
248 ///////////////////////////////////////////////////////////////////////////////
249 // This gets the value of the <snapshot>_beset cookie, which is in XML, and turns it
251 // <BESet name='test'>
252 // <BackendUrl backend='tagserver' url='bingo'/>
255 // Into this (note that everything is optional):
256 // { besetName: 'test',
258 // tagserver: 'bingo', ... }
260 // If there is no cookie set or parsing fails, this returns {}.
262 function cookieXmlToJson(cookieName) {
267 cookieStr = getCookie(cookieName);
268 //console.info("cookie value is '" + cookieStr + "'");
272 var cookieXml = $j(cookieStr);
278 var besetElem = cookieXml.find('BESet');
279 if (besetElem.length == 0) {
280 // No valid cookie value found.
284 var besetName = besetElem.attr("name");
286 cookieObj.besetName = besetName;
289 var backends = besetElem.find("backend");
290 if (backends.length != 0) {
291 backends.each(function (i) {
292 var e = $j(backends[i]);
293 cookieObj.backendUrls[e.attr("name")] = e.text();
294 //console.info("Setting " + e.attr("backend") + ": " + e.attr("url"));
301 ///////////////////////////////////////////////////////////////////////////////
302 function getCookie(name) {
303 var allCookies = document.cookie;
304 //console.info("allCookies = " + allCookies);
305 var pos = allCookies.indexOf(name + "=");
307 var start = pos + (name + "=").length;
308 var end = allCookies.indexOf(";", start);
309 if (end == -1) end = allCookies.length;
310 return decodeURIComponent(allCookies.substring(start, end));
324 // This script was written by Steve Fenton
\r
325 // http://www.stevefenton.co.uk/Content/Jquery-Side-Content/
\r
326 // Feel free to use this jQuery Plugin
\r
329 var classModifier = "";
\r
330 var sliderCount = 0;
\r
331 var sliderWidth = "400px";
\r
333 var attachTo = "rightside";
\r
335 var totalPullOutHeight = 0;
\r
337 function CloseSliders (thisId) {
\r
338 // Reset previous sliders
\r
339 for (var i = 0; i < sliderCount; i++) {
\r
340 var sliderId = classModifier + "_" + i;
\r
341 var pulloutId = sliderId + "_pullout";
\r
343 // Only reset it if it is shown
\r
344 if ($("#" + sliderId).width() > 0) {
\r
346 if (sliderId == thisId) {
\r
347 // They have clicked on the open slider, so we'll just close it
\r
348 showSlider = false;
\r
351 // Close the slider
\r
352 $("#" + sliderId).animate({
\r
356 // Reset the pullout
\r
357 if (attachTo == "leftside") {
\r
358 $("#" + pulloutId).animate({
\r
362 $("#" + pulloutId).animate({
\r
370 function ToggleSlider () {
\r
371 var rel = $(this).attr("rel");
\r
373 var thisId = classModifier + "_" + rel;
\r
374 var thisPulloutId = thisId + "_pullout";
\r
375 var showSlider = true;
\r
377 if ($("#" + thisId).width() > 0) {
\r
378 showSlider = false;
\r
381 CloseSliders(thisId);
\r
384 // Open this slider
\r
385 $("#" + thisId).animate({
\r
389 // Move the pullout
\r
390 if (attachTo == "leftside") {
\r
391 $("#" + thisPulloutId).animate({
\r
395 $("#" + thisPulloutId).animate({
\r
404 $.fn.sidecontent = function (settings) {
\r
407 classmodifier: "sidecontent",
\r
408 attachto: "rightside",
\r
411 pulloutpadding: "5",
\r
412 textdirection: "vertical",
\r
413 clickawayclose: false
\r
417 $.extend(config, settings);
\r
420 return this.each(function () {
\r
424 // Hide the content to avoid flickering
\r
425 $This.css({ opacity: 0 });
\r
427 classModifier = config.classmodifier;
\r
428 sliderWidth = config.width;
\r
429 attachTo = config.attachto;
\r
431 var sliderId = classModifier + "_" + sliderCount;
\r
432 var sliderTitle = config.title;
\r
434 // Get the title for the pullout
\r
435 sliderTitle = $This.attr("title");
\r
437 // Start the totalPullOutHeight with the configured padding
\r
438 if (totalPullOutHeight == 0) {
\r
439 totalPullOutHeight += parseInt(config.pulloutpadding);
\r
442 if (config.textdirection == "vertical") {
\r
444 var character = "";
\r
445 for (var i = 0; i < sliderTitle.length; i++) {
\r
446 character = sliderTitle.charAt(i).toUpperCase();
\r
447 if (character == " ") {
\r
448 character = " ";
\r
450 newTitle = newTitle + "<span>" + character + "</span>";
\r
452 sliderTitle = newTitle;
\r
455 // Wrap the content in a slider and add a pullout
\r
456 $This.wrap('<div class="' + classModifier + '" id="' + sliderId + '"></div>').wrap('<div style="width: ' + sliderWidth + '"></div>');
\r
457 var pullout = $('<div class="' + classModifier + 'pullout" id="' + sliderId + '_pullout" rel="' + sliderCount + '">' + sliderTitle + '</div>').insertBefore($("#" + sliderId));
\r
459 // Store reference to the tab element in parent
\r
460 $This.data('pullout', pullout);
\r
462 if (config.textdirection == "vertical") {
\r
463 $("#" + sliderId + "_pullout span").css({
\r
465 textAlign: "center"
\r
470 $("#" + sliderId).css({
\r
471 position: "absolute",
\r
472 overflow: "hidden",
\r
476 opacity: config.opacity
\r
479 // For left-side attachment
\r
480 if (attachTo == "leftside") {
\r
481 $("#" + sliderId).css({
\r
485 $("#" + sliderId).css({
\r
490 // Set up the pullout
\r
491 $("#" + sliderId + "_pullout").css({
\r
492 position: "absolute",
\r
493 top: totalPullOutHeight + "px",
\r
496 opacity: config.opacity
\r
499 $("#" + sliderId + "_pullout").live("click", ToggleSlider);
\r
501 var pulloutWidth = $("#" + sliderId + "_pullout").width();
\r
503 // For left-side attachment
\r
504 if (attachTo == "leftside") {
\r
505 $("#" + sliderId + "_pullout").css({
\r
507 width: pulloutWidth + "px"
\r
510 $("#" + sliderId + "_pullout").css({
\r
512 width: pulloutWidth + "px"
\r
516 totalPullOutHeight += parseInt($("#" + sliderId + "_pullout").height());
\r
517 totalPullOutHeight += parseInt(config.pulloutpadding);
\r
519 var suggestedSliderHeight = totalPullOutHeight + 30;
\r
520 if (suggestedSliderHeight > $("#" + sliderId).height()) {
\r
521 $("#" + sliderId).css({
\r
522 height: suggestedSliderHeight + "px"
\r
526 if (config.clickawayclose) {
\r
527 $("body").click( function () {
\r
532 // Put the content back now it is in position
\r
533 $This.css({ opacity: 1 });
\r
542 /* Override this file with one containing code that belongs on every page of your application */
547 // Set event listener to scroll the nav poppers to the current page when opened
548 $("#source-link-top, #source-link-bottom").bind(
549 "ncbipopperopencomplete",
551 var dest = $(this).attr('href');
552 var selected_link = $(dest).find('.current-toc-entry');
554 if (selected_link.length > 0)
556 $(dest).scrollTo(selected_link, { offset: -100, duration: 400 });
564 // See PMC-7567 - Google suggestions
565 // This is an AJAX implementation of functionality that already exists in the backend.
566 // This Portal/AJAX implementation overrides the backend implementation.
568 // This JS looks for a span with id "esearch-result-number". If it exists,
569 // then it does an ajax call to esearch (at the url specified in the @ref
570 // attribute) and gets the count. It then replaces the contents of this
571 // span with that count. Finally, it shows the outer div.
573 // An example of an esearch query url is
574 // /entrez/eutils/esearch.fcgi?term=unemployed&db=pmc&rettype=count&itool=QuerySuggestion
575 // which would return something like this:
577 // <Count>10573</Count>
580 // PMC-11350 - itool=QuerySuggestion query parameter is required to filter
581 // out such queries while calculating statistics.
583 jQuery(document).ready(
587 $("#esearch-result-number").each(
589 var countSpan = $(this);
590 var esearchUrl = countSpan.attr("ref");
591 if (esearchUrl.length > 0) {
597 success: function(xml) {
598 $(xml).find('Count').each(function(){
599 var count = $(this).text();
601 // Insert the count into the element content,
602 // and show the outer div.
603 countSpan.text(count);
604 $("div#esearchMessageArea").show();
618 // This code uses JavaScript to build search URL and send search request directly to the database
619 // that is asking for the request. This works only for resources at a top-level URL.
620 // If JavaScript is turned off, or a redirect loop would occur, the search form's HTTP GET is
621 // allowed to continue.
623 if (typeof(jQuery) != 'undefined') {
626 // This is the default function that returns the search URL
627 // You can override this by defining NCBISearchBar_searchUrl.
628 var defaultSearchUrl = function() {
629 var db = $('#entrez-search-db');
630 var term = $('#term');
631 if (db && term && db[0] && term[0]) {
633 // The searchUrl is the selected database's data-search-uri attribute, if set; otherwise it's /dbname/.
634 var searchUrl = $("option:selected", db[0]).attr('data-search-uri') || ("/" + db[0].value + "/");
637 (term[0].value.replace(/^\s+/,'').length != 0) ?
638 "?term=" + encodeURIComponent(term[0].value).replace(/%20/,'+') :
641 return searchUrl + termParam;
645 function searchUrl() {
646 // If the user has overridden the URL function:
648 if (typeof(NCBISearchBar_customSearchUrl) != "undefined") {
649 url = NCBISearchBar_customSearchUrl();
652 url = defaultSearchUrl();
657 // Handle search submit request
658 function do_search() {
660 var form = $('#entrez-search-form');
662 // Disable crap portal-injected parameters so they are not sent to search URI
663 $('input[type="hidden"][name^="p$"]', form).each(function() {
664 $(this).attr('disabled', 'disabled');
667 // Get new search URL with term, etc.
668 var search_url = searchUrl();
673 // Use POST for big things, GET for everything else
674 if (search_url.length > 2000) {
675 form.attr('method','POST');
676 form.attr('action',search_url.replace(/\?.*/,''));
678 window.location = search_url;
684 // Copied from Entrez...
685 function search_ping() {
686 var cVals = ncbi.sg.getInstance()._cachedVals;
688 var searchDetails = {}
689 searchDetails["jsEvent"] = "search";
691 var app = cVals["ncbi_app"];
692 var db = cVals["ncbi_db"];
693 var pd = cVals["ncbi_pdid"];
694 var pc = cVals["ncbi_pcid"];
696 var sel = document.getElementById("entrez-search-db");
697 var searchDB = sel.options[sel.selectedIndex].value;
698 var searchText = document.getElementById("term").value;
700 if( app ){ searchDetails["ncbi_app"] = app.value; }
701 if( db ){ searchDetails["ncbi_db"] = db.value; }
702 if( pd ){ searchDetails["ncbi_pdid"] = pd.value; }
703 if( pc ){ searchDetails["ncbi_pcid"] = pc.value; }
704 if( searchDB ){ searchDetails["searchdb"] = searchDB;}
705 if( searchText ){ searchDetails["searchtext"] = searchText;}
707 ncbi.sg.ping(searchDetails);
710 // User function NCBISearchBar_handle_autocomp(), if defined, must handle search request, including submit, if any
711 function autocomp_select(event, sgData) {
712 var term = $('#term');
713 if (typeof(NCBISearchBar_handle_autocomp) != 'undefined') {
714 NCBISearchBar_handle_autocomp(event, sgData);
716 if (!term.val().trim()) {
717 $('#term').val(sgData.optionSelected||sgData.userTyped);
720 // Only explicitly trigger submit if user didn't.
721 if (sgData.optionIndex >= 0) {
722 $('#entrez-search-form').submit();
727 $(document).ready(function() {
729 var db = $('#entrez-search-db');
730 var term = $('#term');
731 var form = $('#entrez-search-form');
733 db.removeAttr('disabled'); // Reenable if this is backbutton
735 // Handle autocomplete events
736 term.bind("ncbiautocompleteenter", autocomp_select ).bind("ncbiautocompleteoptionclick", autocomp_select );
738 // If form is submitted, handle POST (if necessary) and logging.
739 form.submit(do_search);
741 // Turn autocomplete on or off depending on whether the new database
742 // has an autocomplete dict defined on the selected option.
743 $('#entrez-search-db').change(function() {
744 var acdict = $('#entrez-search-db option:selected').attr('data-ac-dict');
746 $("#term").ncbiautocomplete("option","isEnabled",true).ncbiautocomplete("option","dictionary",acdict);
747 console.info("Setting autocomplete dictionary to " + acdict);
749 console.info("Disabling autocomplete dictionary");
750 $("#term").ncbiautocomplete("turnOff");
753 }); // End document.ready
754 })(jQuery); // Close scope
761 (function( $ ){ // pass in $ to self exec anon fn
762 var post_url = '/myncbi/session-state/';
767 $('div.portlet, div.section').each( function() {
769 // get the elements we will need
771 var anchor = self.find('a.portlet_shutter');
772 var content = self.find('div.portlet_content, div.sensor_content');
774 // we need an id on the body, make one if it doesn't exist already
775 // then set toggles attr on anchor to point to body
776 var id = content.attr('id') || $.ui.jig._generateId('portlet_content');
778 anchor.attr('toggles', id);
779 content.attr('id', id);
781 // initialize jig toggler with proper configs, then remove some classes that interfere with
783 var togglerOpen = anchor.hasClass('shutter_closed') ? false : true;
787 initOpen: togglerOpen
789 removeClass('ui-ncbitoggler-no-icon').
790 removeClass('ui-widget');
792 // get rid of ncbitoggler css props that interfere with portlet styling, this is hack
793 // we should change how this works for next jig release
794 anchor.css('position', 'absolute').
797 /* self.find( 'div.ui-helper-reset' ).
798 removeClass('ui-helper-reset');
800 content.removeClass('ui-widget').
803 // trigger an event with the id of the node when closed
804 anchor.bind( 'ncbitogglerclose', function() {
805 anchor.addClass('shutter_closed');
807 $.post(post_url, { section_name: anchor.attr('pgsec_name'), new_section_state: 'true' });
810 anchor.bind('ncbitoggleropen', function() {
811 anchor.removeClass('shutter_closed');
812 $.post(post_url, { section_name: anchor.attr('pgsec_name'), new_section_state: 'false' });
817 /* Popper for brieflink */
818 $('li.brieflinkpopper').each( function(){
819 var $this = $( this );
820 var popper = $this.find('a.brieflinkpopperctrl') ;
821 var popnode = $this.find('div.brieflinkpop');
822 var popid = popnode.attr('id') || $.ui.jig._generateId('brieflinkpop');
823 popnode.attr('id', popid);
825 destSelector: "#" + popid,
826 destPosition: 'top right',
827 triggerPosition: 'middle left',
829 arrowDirection: 'right',
830 isTriggerElementCloseClick: false,
832 openAnimation: 'none',
833 closeAnimation: 'none',
837 });// end on page ready
840 (function( $ ){ // pass in $ to self exec anon fn
845 $('li.ralinkpopper').each( function(){
846 var $this = $( this );
848 var popnode = $this.find('div.ralinkpop');
849 var popid = popnode.attr('id') || $.ui.jig._generateId('ralinkpop');
850 popnode.attr('id', popid);
852 destSelector: "#" + popid,
853 destPosition: 'top right',
854 triggerPosition: 'middle left',
856 arrowDirection: 'right',
857 isTriggerElementCloseClick: false,
859 openAnimation: 'none',
860 closeAnimation: 'none',
865 });// end on page ready
870 function historyDisplayState(cmd)
872 var post_url = '/myncbi/section-state/';
874 if (cmd == 'ClearHT')
876 if (!confirm('Are you sure you want to delete all your saved Recent Activity?'))
882 var ajax_request = jQuery.post(post_url, { history_display_state: cmd })
883 .complete(function(jqXHR, textStatus) {
885 var htdisplay = jQuery('#HTDisplay');
886 var ul = jQuery('#activity');
890 // so that the following msg will show up
891 htdisplay.removeClass();
893 if (jqXHR.status == 408)
895 htdisplay.html("<p class='HTOn'>Your browsing activity is temporarily unavailable.</p>");
899 if (htdisplay.find('#activity li').length > 0)
901 ul.removeClass('hide');
905 htdisplay.addClass('HTOn');
909 else if (cmd == 'HTOff')
912 htdisplay.removeClass().addClass('HTOff'); // make "Activity recording is turned off." and the turnOn link show up
914 else if (cmd == 'ClearHT')
916 if (htdisplay.attr('class') == '')
918 htdisplay.addClass('HTOn'); // show "Your browsing activity is empty." message
920 ul.removeClass().addClass('hide');
933 This code is adapted from the JS that Aaron Cohen wrote, in PPMCArticlePageJS.
934 I (cfm) use the term CRB (cited reference block) to refer to the portlets on
935 an article page that are aligned with the paragraphs.
937 This module performs two main functions:
938 - Initializes and manages the CRBs, and the links within them.
939 These come as div.pmc_para_cit elements from the backend.
940 - Initializes and manages the poppers on the links within the
941 body of the article (called body links). There are two types:
942 - Body links that have a corresponding CRB link. These will,
943 for the most part, correspond to citations to articles that
944 are in PubMed. In these cases, the popper text gets cloned
946 - Body links that do not have a corresponding CRB link. These
947 are mostly citations that don't have a corresponding PubMed
948 entry. In these cases, the popper text gets cloned from the
953 The CRBs should all be rendered on the page in their fully-expanded
954 states, in the discovery column. They should appear below any "fixed"
955 portlets -- which are portlets of the normal kind, that appear
956 one-after-another at the top of the disco column. They should have no
959 They should all have the class pmc_para_cit. In the CSS, we initially
960 set the visibility of these to "hidden", to prevent the flash of unstyled
963 For reference, here are the jQuery data items that we save with the
966 - CRB (div.pmc_para_cit)
967 - myParagraph - reference to the jQuery wrapper object for the
968 paragraph corresponding to this CRB.
969 - myTop - integer - top of this CRB. This is constant and is
970 set during initialization. If there's no para corresponding
971 to this CRB (edge case), then this will be 0.
972 - expandedHeight - int
973 - collapsedHeight - int
974 - collapsedNumLinks - int
975 - isCollapsed - boolean
976 - autoCollapseTimerId - int
978 - Links in a CRB (div.pmc_para_cit li):
979 - myCrb - ref. to the jQuery object for this link's containing CRB.
980 - expandedHeight - int
981 - myBodyLinks - reference to the jQuery object that holds the
982 (one-to-many) body links that correspond to the *same citation*
983 as this CRB link. Note that this is *not* the complement of the
984 body link's myCrbLink data member. Here's the difference. For
985 a body link to have a myCrbLink pointing to this CRB link, it
986 must be in the paragraph corresponding to this CRB. But, the
987 CRB link's myBodyLinks lists *all* of the links in the body,
988 regardless of paragraph, that correspond to the same citation.
991 - isCollapsed - boolean
994 - myCrbLink - reference to the jQuery object for the
995 CRB <li> corresponding to this citation link in the body.
996 - ncbipopper - reference to the jQuery object for this link's
1001 // We use an IIFE in order to not pollute the global namespace.
1004 // This constant can be set to control the maximum number of
1005 // links ever to show in a CRB. But note that this is already
1006 // coded in the xslt that generates the portlet on the server.
1007 // So we probably don't need this. Anyway, it might be nice to
1008 // have. So I'll keep it and set it to a high value.
1009 var MAX_LINKS_PER_CRB = 50;
1011 // Specify a time delay before the CRB will "auto-collapse".
1012 var AUTOCOLLAPSE_TIMEOUT = 4000;
1014 // Attach the initialize() function to the window load event.
1015 // I used to think this was only necessary for Safari, but see
1016 // PMC-14368, and this SE post:
1017 // http://stackoverflow.com/questions/544993/official-way-to-ask-jquery-wait-for-all-images-to-load-before-executing-somethin
1018 $j(window).load(initialize);
1020 //-------------------------------------------------------------
1021 // This initialize function runs when the DOM is ready.
1022 // All the other function definitions are nested inside this one,
1023 // so that they close over the local variables that are initialized
1026 function initialize() {
1028 // Get the list of all CRBs in the document
1029 var crbs = $j('div.pmc_para_cit');
1030 var numCrbs = crbs.length;
1033 // We need to hide all CRBs that interfere with one of the main
1034 // discovery portlets. We'll assume that these main portlets all
1035 // appear at the top. So we have to find out the bottom-most
1036 // boundary of all of these. This calculation is in a function,
1037 // because it gets re-computed every time one of those fixed
1038 // portlets expands or collapses.
1040 var fixedPortlets = $j('div.portlet').not('div.pmc_para_cit');
1041 var mainPortletsBottom = getMainPortletsBottom();
1043 // Attach a handler to all of these fixed portlets, that gets activate
1044 // whenever a shutter button clicks. Since the shutter button click
1045 // causes the size to change, we have to re-hide/show CRBs as necessary.
1046 fixedPortlets.find("a.portlet_shutter")
1047 .click(fixedPortletShutterClick);
1049 // These will store the values of the inner and outer height
1050 // of a 'collapsed' (two_line) link. But note that since the links
1051 // are initially in the expanded state, we have to defer grabbing these
1052 // values until the first time we collapse one of them.
1053 // (linkHeight == -1) marks that we haven't gotten the values yet.
1054 var linkHeight = -1;
1055 var linkOuterHeight;
1057 // This points to the current expanded CRB, or to null if there isn't one.
1058 var currentExpandedCrb = null;
1060 // Set the hoverintent mouseover event handlers for all <li>s.
1061 // I tweaked sensitivity and interval so that it's not quite so
1062 // sensitive -- I think otherwise users are likely to get annoyed.
1063 crbs.find('li').hoverIntent({
1064 over: hoverOverCrbLink,
1065 out: hoverOutCrbLink,
1070 // Set a mouse-over event on all CRBs to control an automatic timer.
1071 // The timer causes the CRB to re-collapse if the user hasn't had her
1072 // mouse over it for a while.
1073 crbs.mouseover(clearAutoCollapseTimerHandler);
1074 crbs.mouseleave(setAutoCollapseTimerHandler);
1076 // Set up the CRB loop driver. The CRBs are initialized a few at a time in
1077 // a loop that's called once a millisecond. This progressive rendering
1078 // improves responsiveness when the page first loads.
1080 // This is a pointer into the array of CRBs, that's used by the driver loop.
1082 // How many CRBs to process with each iteration of the driver loop.
1083 var CRB_BATCH_SIZE = 3;
1085 // Next add poppers for every link in the body of the text. This also
1086 // uses a driver loop. Set it up here.
1088 // We're interested in all links that have 'bibr' class, and that
1089 // have a @rid attribute.
1090 var allBodyLinks = $j('a.bibr[rid]');
1091 var numBodyLinks = allBodyLinks.length;
1092 // This acts as a pointer into the array of links, that's used by the
1094 var bodyLinkNum = 0;
1095 // How many links to process with each iteration of the driver loop.
1096 var BODY_LINK_BATCH_SIZE = 10;
1097 // This is the container div for the poppers that we'll create. This
1098 // should be present in the document from the server.
1099 var blPopperDiv = $j('#body-link-poppers');
1101 // We'll keep track of all the poppers we create, indexed by the @rid
1102 // value of the link. That way, we're reuse duplicate poppers.
1105 // The driver loops are chained together. The order should not matter.
1106 // At the end of both driver loops, then we set hoverIntent event handlers
1107 // on the body links. This step must be performed last. That's because
1108 // both the ncbipopper (set up by the body link driver loop) and the CRB
1109 // functionality (which sets hover events to highlight the text and the
1110 // corresponding links in the CRB) use hoverIntent; so the events
1111 // conflict. To fix it, I put calls to the ncbipopper open() and
1112 // close() methods at the end of the CRB hover event handlers, thus
1113 // chaining the handlers together.
1117 //-----------------------------------------------------------------
1118 // Here is the CRB driver loop, which handles a few CRBs, and then
1119 // queues itself up to execute again after one ms.
1120 function crbDriverLoop() {
1121 for (var i = 0; i < CRB_BATCH_SIZE; ++i) {
1122 if (crbNum >= numCrbs) break;
1123 positionOneCrb($j(crbs[crbNum]));
1126 // Set up to call ourselves after one millisecond
1127 if (crbNum < numCrbs) {
1128 setTimeout(crbDriverLoop, 1);
1130 // When this driver loop is done, it daisy-chains to the
1133 setTimeout(bodyLinkDriverLoop, 1);
1137 //-----------------------------------------------------------------
1138 // This is a similar driver loop for setting the poppers on all
1139 // of the body links.
1140 function bodyLinkDriverLoop() {
1141 for (var i = 0; i < BODY_LINK_BATCH_SIZE; ++i) {
1142 if (bodyLinkNum >= numBodyLinks) break;
1143 addBodyLinkPopper($j(allBodyLinks[bodyLinkNum]));
1146 // Set up to call ourselves after one millisecond
1147 if (bodyLinkNum < numBodyLinks) {
1148 setTimeout(bodyLinkDriverLoop, 1);
1151 // When this driver loop is done, it invokes a function that
1152 // sets all of the custom hoverIntent handlers on the body links.
1154 // PMC-15007 - Took this out. It was causing the popper to go away whenever
1155 // you mouseout of the trigger link. This was causing the flashing problem
1156 // described in that ticket. But worse, it was preventing you from being able
1157 // to mouse over the popper without the popper going away.
1159 //setTimeout(setBodyLinkHoverHandlers, 1);
1163 //-----------------------------------------------------------------
1164 // This sets the hoverIntent handlers on all body links. As mentioned
1165 // above, this must be done last.
1166 // PMC-15007 - this is not called. See above.
1167 function setBodyLinkHoverHandlers() {
1170 over: hoverOverBodyLink,
1171 out: hoverOutBodyLink,
1177 //-----------------------------------------------------------------
1178 // This function absolutely positions one of the CRBs. For progressive-
1179 // rendering, this gets called from the driver loop above.
1180 function positionOneCrb(jcrb)
1182 // Get the paragraph to which this CRB corresponds
1183 var rid = jcrb.attr('rid');
1184 var p = $j('#' + rid);
1185 // If there is no paragraph, just hide the CRB and return
1186 if (p.length != 1) {
1188 jcrb.data('myTop', 0);
1191 // Record this paragraph
1192 jcrb.data('myParagraph', p);
1195 // We'll compute the "visual top", which takes into account the
1196 // reported top, the top margin, and a fudge factor.
1197 var myTop = parseInt(p.position().top) + parseInt(p.css('marginTop')) - 7;
1198 jcrb.data('myTop', myTop);
1200 // Do some work for each link within this CRB
1201 var crbLinks = jcrb.find('li');
1202 crbLinks.each(function() {
1205 // Get the expanded heights of each of the <li>s.
1206 var h = li.height();
1209 // Find the matching links in the body (might be several)
1210 var referenceId = li.attr('reference_id');
1211 var bodyLinks = p.find('a.bibr[rid="' + referenceId + '"]');
1213 // Set a data pointer from the body links back to this CRB link
1214 bodyLinks.data('myCrbLink', li);
1216 // Now find myBodyLinks. Note that this is *not* the same as the
1217 // bodyLinks above. Here's the difference. For
1218 // a body link to have a myCrbLink pointing to this CRB link, it
1219 // must be in the paragraph corresponding to this CRB. But, the
1220 // CRB link's myBodyLinks lists *all* of the links in the body,
1221 // regardless of paragraph, that correspond to the same citation.
1222 var myBodyLinks = $j('a.bibr[rid="' + referenceId + '"]');
1224 // Store these data in the CRB link
1226 'myCrb': jcrb, // reference from link back to containing CRB
1227 'expandedHeight': h,
1228 'myBodyLinks': myBodyLinks
1232 // Now collapse the CRB into its default resting state
1234 // First collapse each of the <li>s by adding the 'two_line' class.
1235 crbLinks.each(function() {
1237 li.removeClass('expanded')
1238 .removeClass('highlight')
1239 .addClass('two_line');
1240 li.data('isCollapsed', true);
1242 // If this is the first-ever 'two_line' link that we've seen,
1243 // then record its heights
1244 if (linkHeight == -1) {
1245 linkHeight = li.height();
1246 linkOuterHeight = li.outerHeight(true);
1250 // Get the height of the paragraph. This gives the maximum height
1251 // of the 'collapsed' CRB. In other words, in the collapsed state,
1252 // the CRB should not extend below the bottom of the para.
1253 var pHeight = p.height();
1255 // The number of links in this CRB, as delivered from the server.
1256 var numLinks = crbLinks.length;
1258 // Calculate how many links will fit on this CRB, according to the
1259 // height of the paragraph.
1260 // Note that this is a little bit tricky. When we figure out whether
1261 // or not we need to hide links, and, as a consequence, add the "see more"
1262 // link, then we *do not* take the height of the "see more" link into
1263 // account. But, once we know that we need to hide links, and thus add
1264 // the "see more" link, then we need to take the height of that link into
1265 // account to figure out *how many links to hide*.
1266 var linksFit = Math.floor( pHeight / linkOuterHeight );
1268 // Maximum number of links to show here is based either on policy
1269 // (MAX_LINKS_PER_CRB) or on the number that will fit.
1270 var maxLinks = Math.min(MAX_LINKS_PER_CRB, linksFit);
1272 // See if we need to hide some.
1273 if (numLinks > maxLinks) {
1274 // We need to take off some links. But first, let's add the "more"
1275 // link, and measure the total expanded height.
1277 // Add the "more" link. This gets appended as the next sibling of
1278 // the <ul>. Measure the height before and after, so we know how
1279 // much height this link contributes.
1280 var heightBefore = jcrb.height();
1282 var moreLink = $j("<a href='' class='seemore'>See more ...</a>");
1283 jcrb.find('div.portlet_content').append(moreLink);
1285 // Add the event handler.
1286 moreLink.click(moreLinkClick);
1288 // Measure the expanded height
1289 var expandedHeight = jcrb.height();
1291 // Figure out how much the "see more" link added
1292 var moreLinkHeight = jcrb.height() - heightBefore;
1294 // Based on this new information, figure out how many links we will
1295 // show (never go below 0).
1297 Math.max(0, Math.floor( (pHeight - moreLinkHeight) / linkOuterHeight ));
1299 // Now hide the excess links. For each, record that it is an extra.
1300 for (var i = linksToShow; i < numLinks; i++) {
1303 .data('isExtra', true);
1306 // If we're showing no links, change "See more ..." to "See links ..."
1307 if (linksToShow <= 0) moreLink.text("See links ...");
1309 // Measure the height again and save as collapsedHeight
1310 var collapsedHeight = jcrb.height();
1312 // Now store a bunch of this data with the CRB DOM object
1314 'expandedHeight': expandedHeight,
1315 'collapsedHeight': collapsedHeight,
1316 'collapsedNumLinks': linksToShow
1320 // Whether or not we need to hide any links, initialize
1321 // the isCollapsed flag, and the autoCollapseTimerId.
1323 'isCollapsed': true,
1324 'autoCollapseTimerId': -1
1327 // Capture the width prior to repositioning. Since we're changing
1328 // from position-static to position-absolute, we'll need to set
1329 // this to a fixed value.
1330 var width = jcrb.css('width');
1332 // The final thing we do is to position it next to the para,
1333 // and make it visible.
1335 'position': 'absolute',
1336 'visibility': 'visible',
1341 // If it would interfere with one of the fixed portlets, hide it.
1342 if (myTop <= mainPortletsBottom) jcrb.hide();
1345 //-----------------------------------------------------------------
1346 // This function handles the mouseover event when the user hovers
1347 // over one of the CRB links items.
1348 function hoverOverCrbLink(event) {
1349 //console.info("hoverOverCrbLink");
1350 var link = $j(this);
1351 // Call expandLink with checkHidden false - we know its not
1352 // hidden, because the user has his mouse over it.
1353 expandLink(link, false);
1356 //-----------------------------------------------------------------
1357 // This gets called to highlight a particular link in a CRB.
1358 // It checkHidden is true, then this first checks to see if this
1359 // link's CRB is collapsed, and this link is hidden. If so, then
1360 // this first expands the CRB.
1361 // [Note that the 'checkHidden' feature is not used. We originally
1362 // implemented this so that if the user hovers over a body link
1363 // corresponding to an "extra" CRB link, then that would cause the
1364 // CRB to expand to show it. But David didn't like so much animation
1365 // going on in the disco column while the user is moving his mouse
1366 // around over the body. Even though it's not used, I left the code in.]
1368 function expandLink(link, checkHidden) {
1369 //console.info("expandLink");
1370 var jcrb = link.data('myCrb');
1371 jcrb.addClass("stretched");
1372 // Set the z-index. This ensures that when the CRBs are expanded, they
1373 // are layered on top of ones that are collapsed.
1374 jcrb.css('z-index', 1);
1376 // Enforce the policy that only one CRB is stretched at a time.
1377 collapseOtherCrbs(jcrb);
1379 // See if we need to expand the whole CRB (equivalent to "see more").
1381 link.data('isExtra') &&
1382 jcrb.data('isCollapsed') )
1387 var h = link.data('expandedHeight');
1388 link.data('isCollapsed', false);
1390 // Animate the height change. Whenever we do this, we have
1391 // to specify the exact numeric value of the final height.
1392 // When done, we set the CSS property back to 'auto'.
1395 { 'complete': function() {
1396 jcrb.css({height: "auto"});
1402 // Do this *after* kicking off the animation, because this is
1403 // what causes the actual height of the link to change.
1404 link.removeClass('two_line')
1405 .addClass('expanded');
1407 // This causes the link in the CRB, and all its corresponding
1408 // links in the body, to be highlighted with a special color.
1409 highlightLink(link);
1413 //-----------------------------------------------------------------
1414 // This function handles the event when the user moves the mouse
1415 // out of the <li> items.
1416 function hoverOutCrbLink(event) {
1417 //console.info("hoverOutCrbLink");
1418 var link = $j(this);
1422 //-----------------------------------------------------------------
1423 function unexpandLink(link) {
1424 //console.info("unexpandLink");
1425 var jcrb = link.data('myCrb');
1427 link.removeClass('expanded')
1428 .addClass('two_line');
1429 // Remove the highlight color from the link in the CRB and
1430 // all the corresponding links in the body.
1431 unhighlightLink(link);
1434 {'height': linkHeight},
1435 { 'complete': function() {
1436 link.data('isCollapsed', true);
1437 // Check if we still need the 'stretched' class on the
1439 checkStretched(jcrb);
1440 // Reset the height property to auto.
1441 jcrb.css({height: "auto"});
1449 //-----------------------------------------------------------------
1450 // This function handles the hovering over a body link. Note that
1451 // some of these will have corresponding CRB links, and some will
1453 function hoverOverBodyLink(event) {
1454 //console.info("hoverOverBodyLink");
1455 var bodyLink = $j(this);
1457 /* PMC-15150 - reduce "christmas tree effect", don't highlight
1458 CRB when user hovers over body link.
1460 var crbLink = bodyLink.data('myCrbLink');
1462 highlightLink(crbLink);
1463 var jcrb = crbLink.data('myCrb');
1464 clearAutoCollapseTimer(jcrb);
1468 // If there's a popper associated with this, open it.
1469 // Both this and JIG's ncbipopper use hoverIntent, so we have
1470 // to chain these events together.
1471 var popper = bodyLink.data('ncbipopper');
1472 if (popper) popper.open();
1475 //-----------------------------------------------------------------
1476 function hoverOutBodyLink(event) {
1477 //console.info("hoverOutBodyLink");
1478 var bodyLink = $j(this);
1479 var crbLink = bodyLink.data('myCrbLink');
1481 unhighlightLink(crbLink);
1482 var jcrb = crbLink.data('myCrb');
1483 setAutoCollapseTimer(jcrb);
1486 // If there's a popper associated with this, close it
1487 var popper = bodyLink.data('ncbipopper');
1488 if (popper) popper.close();
1491 //-----------------------------------------------------------------
1492 // This function highlights a CRB link, and all of the body
1493 // links that correspond to it.
1494 function highlightLink(link) {
1495 //console.info("highlightLink");
1496 link.addClass('highlight');
1497 link.data('myBodyLinks').addClass('highlight');
1500 //-----------------------------------------------------------------
1501 // This function unhighlights a CRB link, and all of the body links
1502 // that correspond to it.
1503 function unhighlightLink(link) {
1504 //console.info("unhighlightLink");
1505 link.removeClass('highlight');
1506 link.data('myBodyLinks').removeClass('highlight');
1509 //-----------------------------------------------------------------
1510 // This function removes, when necessary, the 'stretched' class on
1511 // the CRB object. If the jcrb isCollapsed is true, and all of
1512 // the links' isCollapsed are true, then clear this class. Note
1513 // that we don't check whether or not to *set* the class - that is
1514 // done instantly whenever any of those things starts to expand.
1515 function checkStretched(jcrb) {
1516 //console.info("checkStretched");
1517 if (!jcrb.data('isCollapsed')) return;
1519 var hasExpandedLink = false;
1520 jcrb.find('li').each(function() {
1521 if (!$j(this).data('isCollapsed')) hasExpandedLink = true;
1523 if (hasExpandedLink) return;
1525 jcrb.removeClass('stretched');
1526 jcrb.css('z-index', 0);
1529 //-----------------------------------------------------------------
1530 // Click handler for the "see more" link at the bottom of the portlet.
1531 // Note that this might be either a "see more" or a "see less" click.
1532 function moreLinkClick(event) {
1533 //console.info("moreLinkClick");
1534 var moreLink = $j(this);
1535 var jcrb = moreLink.parents('div.pmc_para_cit');
1537 // If we're collapsed, then we need to expand
1538 if (jcrb.data('isCollapsed')) {
1545 // Override the default action
1549 //-----------------------------------------------------------------
1550 // This function animates the expansion of a CRB to show all of the
1551 // links. We will never call this unless this is a collapsible CRB.
1552 // (In other words, if all the links fit within the height of the
1553 // paragraph, then we'll guarantee that we'll never call this
1556 function expandCrb(jcrb) {
1557 //console.info("expandCrb");
1559 // Make sure this CRB is now on top
1560 collapseOtherCrbs(jcrb);
1562 // Update the state variable
1563 jcrb.data('isCollapsed', false);
1564 currentExpandedCrb = jcrb;
1566 // Add the 'stretched' class to the outer div
1567 jcrb.addClass("stretched");
1568 jcrb.css('z-index', 1);
1570 // Change the label on the "more" link
1571 jcrb.find('.seemore').text("See less ...");
1573 // Animate the height change.
1574 var d = jcrb.data();
1576 {'height': d.expandedHeight},
1577 { 'complete': function() {
1578 jcrb.css({height: "auto"});
1584 // Show all the links. This is done *after* we kick off
1585 // the animation because this is the step that causes
1586 // the actual height to change.
1587 var crbLinks = jcrb.find('ul li.two_line');
1588 var totalNumLinks = crbLinks.length;
1589 for (var i = d.collapsedNumLinks; i < totalNumLinks; ++i) {
1590 $j(crbLinks[i]).show();
1594 //-----------------------------------------------------------------
1595 // This function animates the collapse of the CRB into it's default
1596 // resting state, including ensuring that all child links are in
1597 // their two_line, collapsed state. This gets called from two
1598 // places: when the user clicks on "see less", and when the
1599 // auto-collapse timer triggers.
1601 function collapseCrb(jcrb) {
1602 //console.info("collapseCrb");
1604 var d = jcrb.data();
1606 // Change the label on the link
1607 var linkText = d.collapsedNumLinks > 0 ? "See more ..." : "See links ...";
1608 jcrb.find('.seemore').text(linkText);
1610 // Get the link children
1611 var crbLinks = jcrb.find('ul li');
1612 var totalNumLinks = crbLinks.length;
1614 // Animate the height change. We use the 'complete' option of
1615 // the animation function to hide all of the excess links after
1616 // the animation is done. If we hid them all right away, the
1617 // height would change abruptly (no animation).
1619 { 'height': d.collapsedHeight },
1620 { 'complete': function() {
1622 crbLinks.each(function() {
1625 // Put the link into its collapsed state
1626 li.removeClass('expanded')
1627 .removeClass('highlight')
1628 .addClass('two_line');
1629 li.data('isCollapsed', true);
1631 // Hide 'overflow' links
1632 if (li.data('isExtra')) li.hide();
1635 // Now put the CRB object itself into the collapsed state
1636 jcrb.data('isCollapsed', true);
1637 jcrb.removeClass('stretched');
1638 jcrb.css('z-index', 0);
1640 // Reset the height property to auto.
1641 jcrb.css({height: "auto"});
1647 currentExpandedCrb = null;
1650 //-----------------------------------------------------------------
1651 // This function sets a timer on a CRB object so that it will
1652 // reclose after a fixed period of time after the last time that
1653 // the user has moved his/her mouse out of the div. This is set
1654 // up as a handler for mouseleave events. Note that it the CRB
1655 // is already collapsed, this does nothing.
1657 // Handler for the mouse-out event for the whole CRB div.
1658 function setAutoCollapseTimerHandler(event) {
1659 //console.info("setAutoCollapseTimerHandler");
1660 setAutoCollapseTimer($j(this));
1663 // This does the work. It is also called from the handler for
1664 // the mouse-out event on a corresponding body link.
1665 function setAutoCollapseTimer(jcrb) {
1666 //console.info("setAutoCollapseTimer");
1667 if (jcrb.data('isCollapsed')) return;
1670 var timerId = window.setTimeout(function() {
1672 }, AUTOCOLLAPSE_TIMEOUT);
1674 // Record it as a data attribute on this object
1675 jcrb.data('autoCollapseTimerId', timerId);
1678 //-----------------------------------------------------------------
1679 // This function clears the auto-collapse timer. It is set up as
1680 // a handler for the mouseover event.
1682 // Handler for the mouse-over event for the whole CRB div.
1683 function clearAutoCollapseTimerHandler(event) {
1684 //console.info("clearAutoCollapseTimerHandler");
1685 clearAutoCollapseTimer($j(this));
1688 // This does the work. It is also called from the handler for
1689 // the mouse-over event on a corresponding body tag.
1690 function clearAutoCollapseTimer(jcrb) {
1691 //console.info("clearAutoCollapseTimer");
1692 var timerId = jcrb.data('autoCollapseTimerId');
1693 if (timerId == -1) return;
1695 window.clearTimeout(timerId);
1696 jcrb.data('autoCollapseTimerId', -1);
1699 //-----------------------------------------------------------------
1700 // This function is used to collapse all of the *other* CRBs.
1701 // It is invoked whenever we highlight a link or expand a CRB.
1702 // This makes sure that the focus is always on only one at a time.
1703 function collapseOtherCrbs(jcrb) {
1704 //var cstr = (currentExpandedCrb) ?
1705 // (", currentExpandedCrb = " + currentExpandedCrb.attr('id')) :
1707 //console.info("collapseOtherCrbs; jcrb = " + jcrb.attr('id') + cstr);
1709 /* This used to loop through all of the CRBs, collapsing all of
1710 them except myId. But now I just keep track of the (at most
1711 one) CRB that's currently expanded, and collapse that.
1712 var myId = jcrb.attr('id');
1714 crbs.each(function() {
1715 var thisCrb = $j(this);
1716 if (thisCrb.attr('id') != myId) {
1717 collapseCrb(thisCrb);
1721 //console.info("Checking, currentExpandedCrb = " + currentExpandedCrb);
1722 if (currentExpandedCrb && currentExpandedCrb.attr('id') != jcrb.attr('id')) {
1723 collapseCrb(currentExpandedCrb);
1727 //-----------------------------------------------------------------
1728 // This function is set as an event handler when the user clicks
1729 // on any of the shutter buttons on any of the fixed portlets at the
1730 // top. When these fixed portlets change size, we have to go
1731 // through all the CRBs again and hide/show depending on whether
1732 // or not they are interfering.
1733 // But, because the portlet size change is animated, the new size
1734 // won't be known for a while, so this in turn sets a timer that
1735 // invokes hideInterfering().
1737 function fixedPortletShutterClick(event) {
1738 setTimeout(hideInterfering, 200);
1741 //-----------------------------------------------------------------
1742 // hideInterfering iterates through all of the CRBs and hides any
1743 // that would interfere with any of the fixed portlets at the top.
1744 // It is called every time the user clicks a shutter button on one
1745 // of the fixed portlets.
1747 function hideInterfering() {
1748 var mainPortletsBottom = getMainPortletsBottom();
1749 crbs.each(function() {
1750 var jcrb = $j(this);
1751 if (jcrb.data('myTop') <= mainPortletsBottom) {
1760 //-----------------------------------------------------------------
1761 // This function gets the y-coordinate of the bottom of the lowest
1762 // of the fixed portlets.
1764 function getMainPortletsBottom() {
1766 fixedPortlets.each(function() {
1768 var top = parseInt(p.position().top) + parseInt(p.css('marginTop'));
1769 var thisBottom = top + p.height();
1770 bottom = Math.max(bottom, thisBottom);
1775 //-----------------------------------------------------------------
1776 // This function adds a popper to one of the links in the body.
1777 // It is called by the driver loop above. The link is guaranteed
1778 // to have the 'bibr' class, and a @rid attribute.
1779 var popperCount = 0;
1780 function addBodyLinkPopper(link) {
1781 // If we never found a container for our poppers in the source
1782 // document, then we can't do anything.
1783 if (blPopperDiv.length < 1) return;
1785 var rid = link.attr('rid');
1786 // Some id's from the backend have special characters, so we
1787 // need to escape them whenever they're used as jQuery selectors.
1788 // See PMC-6384 and http://tinyurl.com/2qfqgc.
1789 var ridEscaped = rid.replace(/:/g, "\\:")
1790 .replace(/\./g, "\\.")
1791 .replace(/·/g, "\\·");
1793 var popperId = "body-link-popper-" + rid; // + "-" + popperCount;
1794 var popperIdEscaped = "body-link-popper-" + ridEscaped; // + "-" + popperCount;
1797 // If we've already made a popper for this particular reference,
1799 var popper = blPoppers[rid];
1801 // Otherwise, we need to make one.
1803 //console.info("Adding pooper for body link, rid = " + rid);
1805 popper = $j("<div/>", {
1807 'class': 'body-link-popper',
1808 'style': 'display: none'
1811 // If this link has a corresponding CRB link, then we'll
1812 // use that for the popper text
1813 var myCrbLink = link.data('myCrbLink');
1815 // Get the popper text from the CRB link
1816 //console.info("Got a CRB link, rid = " + rid);
1817 popper.append(myCrbLink.children("a").contents().clone());
1818 popper.append(myCrbLink.children(".alt-note").children().clone());
1820 // Find the pubmed link, if exists
1821 var $pmidLink = myCrbLink.find("a[href *= 'pubmed']");
1822 //console.info("myCrbLink = %o", myCrbLink);
1823 //console.info("pmidLink = %o", $pmidLink);
1824 var pubmedLink = $pmidLink.length > 0
1825 ? "[<a href='" + $pmidLink.attr('href') + "'>PubMed</a>] "
1827 popper.append("<p>" + pubmedLink + "[" +
1828 "<a href='" + link.attr("href") + "'>Ref list</a>]</p>");
1831 // Otherwise we'll copy the text from the references section.
1833 // Find the text for this popup from the element pointed to by
1834 // @rid. This will be in the reference section of the article.
1835 //console.info("Not a CRB link, rid = " + rid);
1836 var refElem = $j('#' + ridEscaped);
1838 // Check to make sure that we found something
1839 if (refElem.length > 0) {
1841 var citeText = refElem.text();
1842 var popText = $j.trim(citeText);
1844 // If the @href element of the current <a> tag (currElem)
1845 // has a '#' portion, meaning that it is a link to the
1846 // reference list in this same article, then indicate
1847 // that in the popup text.
1848 if (link.attr("href").charAt(0) == '#') {
1849 popText += ' [<a href="' + link.attr("href") + '">Ref list</a>]';
1852 popper.append(popText);
1855 // Put this new popper into the container div.
1856 blPopperDiv.append(popper);
1858 // And record it for posterity.
1859 blPoppers[rid] = popper;
1863 // It would be nice if I could pass the configuration parameters
1864 // to JIG as a JS object; but it doesn't work.
1865 //var popperConfig = {
1866 // 'destSelector': ('#' + popperId)
1868 // Note that I have to set a fixed width here. "auto" doesn't work.
1869 // Nor does setting it in the CSS file.
1871 // FIXME: This is a little debug feature that lets you enable the
1872 // popper's autoAdjust feature. Because of JSL-1519, I have it
1873 // turned off by default.
1874 var loc = window.location.href;
1875 var qs = loc.substring(loc.indexOf("?") + 1);
1876 var qsParams = qs.split(/&/);
1877 var numQsParams = qsParams.length;
1878 var gotAutoAdjust = false;
1879 for (var i = 0; i < numQsParams; ++i) {
1880 if (qsParams[i] == "__autoadjust") gotAutoAdjust = true;
1882 //console.info("qsParams = " + qsParams[0] + ", " + qsParams[1]);
1883 gotAutoAdjust = true;
1884 var adjustFitStr = gotAutoAdjust ? 'autoAdjust' : 'none';
1886 link.addClass('jig-ncbipopper')
1887 .data('jigconfig', {
1888 destSelector: "#" + popperIdEscaped,
1889 isTriggerElementCloseClick: false,
1892 adjustFit: adjustFitStr,
1893 triggerPosition: "top right"
1895 $j.ui.jig.scan(link);
1897 } /* end of initialize() */